﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;
using Mighty.Common;

namespace Mighty.Brain
{
    public class GugongBrain : BasicBrain
    {
        Shape mGiruda;

        /// <summary>
        /// 각 플레이어가 가지게 되는 게임에 대한 정보, 각 이벤트가 호출되기 전에 gameInfo에는 항상 최신의 정보가
        /// 업데이트되어 있다
        /// </summary>
        public GameInfo gameInfo;

        /// <summary>
        /// 게임시작
        ///  * 매 게임마다 초기화해야할 코드가 있다면 여기에 넣어라
        /// </summary>
        public override void GameStartEvent()
        {
        }

        /// <summary>
        /// 베팅시작
        ///  * 카드를 나누어 주고 호출된다. gameInfo.myCards를 참고
        /// </summary>
        public override void BettingReadyEvent()
        {
        }

        /// <summary>
        /// 베팅차례
        ///  * 지금까지 배팅된 정보(gameInfo.bettingHistory) 또는 마지막 pass가 아닌 베팅정보 gameInfo.betting를 참고하여 베팅을 결정한다
        /// </summary>
        public override Betting BettingTicEvent()
        {
            Betting betting = new Betting();

            int candidate = 0;
            int[] numShape = new int[4];
            int[] numGrim = new int[4];
            int[] score = new int[4];
            bool[] existAK = new bool[4];

            HandedCard[] myCards = gameInfo.myCards;

            for (int i = 0; i < 4; i++)
            {
                numShape[i] = 0;
                numGrim[i] = 0;
                score[i] = 0;
                existAK[i] = false;
            }

            // 무늬별 카드 수와 그림카드 수를 센다.
            foreach (Card card in myCards)
            {
                switch (card.shape)
                {
                    case Shape.Clover:
                        numShape[0]++;
                        if (card.number >= 10) numGrim[0]++;
                        if (card.number >= 13) existAK[0] = true;
                        break;
                    case Shape.Diamond:
                        numShape[1]++;
                        if (card.number >= 10) numGrim[1]++;
                        if (card.number >= 13) existAK[1] = true;
                        break;
                    case Shape.Spade:
                        numShape[2]++;
                        if (card.number >= 10) numGrim[2]++;
                        if (card.number >= 13) existAK[2] = true;
                        break;
                    case Shape.Heart:
                        numShape[3]++;
                        if (card.number >= 10) numGrim[3]++;
                        if (card.number >= 13) existAK[3] = true;
                        break;
                }
            }

            betting.isPass = true;

            // 가장 유력한 후보를 선택한다.
            for (int i = 0; i < 4; i++)
            {
                int tempCandidate = 0;

                // 최소한의 조건
                // A나 K가 존재하고, (그림카드가 3장이거나 그림카드 2장에 무늬가 4장 이상)일 경우
                if (existAK[i] && (numGrim[i] >= 3
                    || (numGrim[i] >= 2 && numShape[i] >= 4)))
                {
                    betting.isPass = false;
                    tempCandidate = i;
                }

                // 기존의 후보 무늬와 비교
                if (numGrim[candidate] < numGrim[tempCandidate])
                    candidate = tempCandidate;
                else if (numGrim[candidate] == numGrim[tempCandidate] && numShape[candidate] < numShape[tempCandidate])
                    candidate = tempCandidate;
            }

            if (!betting.isPass)
            {
                if (gameInfo.betting.limit > 0)
                {
                    Betting previousBetting = gameInfo.betting;
                    if (previousBetting.limit < 15)
                    {
                        betting.isPass = false;
                        betting.giruda = (Giruda)candidate;
                        betting.limit = previousBetting.limit + 1;
                    }
                    else
                    {
                        betting.isPass = true;
                    }
                }
                else
                {
                    betting.isPass = false;
                    betting.giruda = (Giruda)candidate;
                    betting.limit = 13;
                }
            }

            return betting;
        }


        /// <summary>
        /// 다른사람의 베팅 정보
        /// </summary>
        public override void BettingTalkEvent()
        {
        }

        /// <summary>
        /// 베팅완료, 주공이 정해졌다. 베팅이 끝났을 경우 처리할 코드가 있다면 여기에 하라.
        /// </summary>
        public override void BettingCompleteEvent()
        {
        }

        /// <summary>
        /// (주공)카드선택, 13장의 카드중 10장의 카드선택. 버릴3장의 카드에 cmdRemove=true로 설정해주어야 한다
        /// 만약 정확히 3장의 카드가 선택되지 않았을 경우에는 새로추가된 3장의 카드가 그대로 버려진다.
        /// ChoiceCardEvent 가 호출된 후에는 선택된 10장의 카드는 myCards[0]부터 차례대로 배치되고 버려진 3장의 카드는
        /// myCards[10]에서부터 배치되어 나중에 어떤 카드가 버려졌는지 알수 있게 해준다
        /// </summary>
        public override HandedCard[] ChoiceCardEvent()
        {
            int remove = 0;
            HandedCard[] myCards = gameInfo.myCards;
            Giruda giruda = gameInfo.betting.giruda;

            for (int i = 0; i <= 12; i++)
                myCards[i].cmdRemove = false;

            // 기루다가 아니고 10, J인 카드 제거
            for (int i = 0; i <= 12; i++)
            {
                if (myCards[i].cmdRemove == false
                    && myCards[i].shape != (Shape)giruda
                    && (myCards[i].number == 10 || myCards[i].number == 11))
                {
                    myCards[i].cmdRemove = true;
                    remove++;
                }

                if (remove == 3)
                    return myCards;
            }

            // 기루다가 아닌 아무 숫자카드 제거
            for (int i = 0; i <= 12; i++)
            {
                if (myCards[i].cmdRemove == false
                    && myCards[i].shape != (Shape)giruda
                    && myCards[i].number < 10)
                {
                    myCards[i].cmdRemove = true;
                    remove++;
                }

                if (remove == 3)
                    return myCards;
            }

            // 기루다가 아닌 아무 카드 제거
            for (int i = 0; i <= 12; i++)
            {
                if (myCards[i].cmdRemove == false
                    && myCards[i].shape != (Shape)giruda)
                {
                    myCards[i].cmdRemove = true;
                    remove++;
                }

                if (remove == 3)
                    return myCards;
            }

            // 아무 카드 제거
            for (int i = 0; i <= 12; i++)
            {
                if (myCards[i].cmdRemove == false)
                {
                    myCards[i].cmdRemove = true;
                    remove++;
                }

                if (remove == 3)
                    return myCards;
            }

            return myCards;
        }


        /// <summary>
        /// 최종베팅정보, 기루다무늬변경가능. 기루다를 변경할 경우 최소+2된 limit정보를 리턴해야 한다
        /// </summary>
        public override Betting LastBettingEvent()
        {
            return gameInfo.betting;
        }

        /// <summary>
        /// 주공의 최종베팅정보
        /// </summary>
        public override void LastBettingTalkEvent()
        {
            this.mGiruda = (Shape)gameInfo.betting.giruda;
        }

        /// <summary>
        /// (주공)프랜드선택. 프랜드를 선택하는 방법은 노프랜드(FriendPlayer.HowToSelect.nofriend),
        /// 초구프랜드(FriendPlayer.HowToSelect.firstwin),카드프랜드(FriendPlayer.HowToSelect.card)가 있다.
        /// </summary>
        public override FriendPlayer ChoiceFriendEvent()
        {
            HandedCard[] myCards = gameInfo.myCards;
            bool giruda = false;
            bool joker = false;

            foreach (HandedCard card in myCards)
            {
                if (card.IsSame(gameInfo.mighty))
                    giruda = true;
                if (card.shape == Shape.Joker)
                    joker = true;
            }

            // 마이티가 없으면 마이티 친구
            if (!giruda)
            {
                gameInfo.friend.howToSelect = FriendPlayer.HowToSelect.card;
                gameInfo.friend.cardCondition = gameInfo.mighty;
            }
            // 마이티가 있고 조커가 없으면 조커 친구
            else if (!joker)
            {
                gameInfo.friend.howToSelect = FriendPlayer.HowToSelect.card;
                gameInfo.friend.cardCondition = new Card(Shape.Joker, 0);
            }
            // 아니면 초구 친구
            else
            {
                gameInfo.friend.howToSelect = FriendPlayer.HowToSelect.firstwin;
            }

            return gameInfo.friend;
        }

        /// <summary>
        /// 주공의 프랜드 정보
        /// </summary>
        public override void ChoiceFriendTalkEvent()
        {
        }

        /// <summary>
        /// 각라운드의시작
        ///  * 매라운드마다 초기화할 코드가 있다면 이부분에 들어가게 된다
        /// </summary>
        public override void RoundReadyEvent()
        {
        }

        /// <summary>
        /// 카드낼차례
        ///  * 각 사용자의 턴에 한번씩 호출되며 낼 카드를 결과값으로 리턴해야 한다. 
        ///  * 사용가능한 카드에는 myCards[i].isPossible 속성이 true로 설정되어 있다. 
        ///  * 만약 사용불가능한 카드를 낼 경우에 시스템은 카드배열에서 제일 처음 만나는 사용가능한 카드를 골라서 사용한다
        /// </summary>
        public override HandedCard RoundTicEvent()
        {
            ArrayList posCards = new ArrayList();

            foreach (HandedCard card in gameInfo.myCards)
            {
                if (card.isPossible)
                    posCards.Add(card);
            }

            // 공격일 때
            if (IsAttacker())   return AttackerTicEvent(posCards);
            // 야당일 때
            else                return DefenderTicEvent(posCards);
        }

        private bool IsAttacker()
        {
            // 주공
            if (gameInfo.myInfo.position == gameInfo.attackPlayer.position)
                return true;
            
            // 친구가 밝혀졌는데 그것이 나이면
            if (gameInfo.friend.founded && gameInfo.myInfo.position == gameInfo.friend.position)
                return true;
            
            // 친구가 아직 밝혀지진 않았더라도 친구 카드 조건이 나이면
            if (gameInfo.friend.howToSelect == FriendPlayer.HowToSelect.card)
            {
                Card friendCard = gameInfo.friend.cardCondition;

                foreach (Card card in gameInfo.myCards)
                {
                    if (card.number == friendCard.number && card.shape == friendCard.shape)
                        return true;
                }
            }

            // 야당
            return false;
        }

        /// <summary>
        /// 주공이나 친구일 때 낼 카드를 선택한다.
        /// </summary>
        private HandedCard AttackerTicEvent(ArrayList posCards)
        {
            HandedCard nextCard = (HandedCard)posCards[0];

            // 초구친구일 때 주공은 가장 약한 카드를 낸다.
            if (gameInfo.roundIdx == 0 && gameInfo.friend.howToSelect == FriendPlayer.HowToSelect.firstwin)
            {
                foreach (HandedCard card in posCards)
                {
                    if (nextCard.number > card.number)
                        nextCard = card;
                }
                return nextCard;
            }

            // 선이다.
            if (gameInfo.roundCardIdx == 0)
            {
                // 조커가 있고 첫 라운드가 아니다.
                foreach (HandedCard card in posCards)
                {
                    if (card.shape == Shape.Joker)
                    {
                        nextCard = card;
                        nextCard.cmdJokerTarget = mGiruda;
                        return nextCard;
                    }
                }

                // 기루다 무늬가 있으면 높은 순으로 낸다.
                foreach (HandedCard card in posCards)
                {
                    if (card.shape == mGiruda && nextCard.number > card.number)
                        nextCard = card;
                }
                return nextCard;
            }
            // 마지막 순서다.
            if (gameInfo.roundCardIdx == 4)
            {
                return LastPlayer(nextCard, posCards);
            }

            Random rand = new Random();
            int r = rand.Next(posCards.Count);
            nextCard = (HandedCard)posCards[r];

            return nextCard;
        }

        /// <summary>
        /// 야당일 때 낼 카드를 선택한다.
        /// </summary>
        private HandedCard DefenderTicEvent(ArrayList posCards)
        {
            HandedCard nextCard = (HandedCard)posCards[0];

            // 마지막 순서다.
            if (gameInfo.roundCardIdx == 4)
            {
                return LastPlayer(nextCard, posCards);
            }

            if (gameInfo.roundIdx > 0)
            {
                int startPlayer = gameInfo.roundHistory[gameInfo.roundIdx - 1].winner;
                int idx = startPlayer;
                bool attacker = false;
                bool friend = false;

                // 친구가 밝혀지지 않았을 경우 우리편이라 가정
                if (!gameInfo.friend.founded)
                    friend = true;

                // 주공이나 친구가 카드를 냈는지 확인
                for (int i = 0; i < gameInfo.roundCardIdx; i++)
                {
                    if (gameInfo.attackPlayer.position == idx)
                        attacker = true;
                    if (gameInfo.friend.founded && gameInfo.friend.position == idx)
                        friend = true;
                }
                
                // 주공과 친구가 모두 나왔을 경우 (물론 친구가 밝혀지지 않았을 경우 주공만 확인)
                if (attacker && friend)
                {
                    // 지금까지의 승자가 주공이나 친구가 아닐 경우
                    if (FindWinner() != gameInfo.attackPlayer.position
                        && gameInfo.friend.founded && FindWinner() != gameInfo.friend.position)
                    {
                        bool exist = false;

                        // 기루다가 아닌 그림카드를 낸다.
                        foreach (HandedCard card in posCards)
                        {
                            if (card.number >= 10 && card.shape != mGiruda)
                            {
                                exist = true;
                                nextCard = card;
                                break;
                            }
                        }

                        if (exist)
                            return nextCard;

                        // 기루다가 아닌 아무 카드를 낸다. 없으면 기루다라도 낸다.
                        foreach (HandedCard card in posCards)
                        {
                            if (card.shape != mGiruda)
                            {
                                nextCard = card;
                                break;
                            }
                        }

                        return nextCard;
                    }

                    // 지금까지의 승자가 주공이나 친구일 경우
                    else
                    {
                        bool win = true;

                        foreach (HandedCard card in posCards)
                        {
                            nextCard = card;

                            for (int i = 0; i < 4; i++)
                            {
                                if (CompareCards(nextCard, gameInfo.round.cardStack[i]) == false)
                                {
                                    win = false;
                                    break;
                                }
                            }

                            // 이길 수 있다.
                            if (win)
                                return nextCard;
                        }
                        // 이길 수 없다.
                        foreach (HandedCard card in posCards)
                        {
                            if (nextCard.number > card.number)
                                nextCard = card;
                        }
                        return nextCard;
                    }
                }
            }

            Random rand = new Random();
            int r = rand.Next(posCards.Count);
            nextCard = (HandedCard)posCards[r];

            return nextCard;
        }

        /// <summary>
        /// 더 강한 카드를 찾는다. card1이 세면 true, card2가 세면 false
        /// </summary>
        private bool CompareCards(Card card1, Card card2)
        {
            Shape firstShape = gameInfo.round.cardStack[0].shape;

            // 마이티면 승리
            if (card1 == gameInfo.mighty) return true;
            if (card2 == gameInfo.mighty) return false;

            // 첫 라운드와 마지막 라운드가 아니면 조커가 승리
            if (gameInfo.roundIdx != 0 && gameInfo.roundIdx != 9)
            {
                if (card1.shape == Shape.Joker) return true;
                if (card2.shape == Shape.Joker) return false;
            }

            // 기루다가 승리
            if (card1.shape == mGiruda && card2.shape != mGiruda) return true;
            if (card1.shape != mGiruda && card2.shape == mGiruda) return false;

            // 둘 다 기루다고 숫자가 높으면 승리
            if (card1.shape == mGiruda && card2.shape == mGiruda && card1.number > card2.number) return true;
            if (card1.shape == mGiruda && card2.shape == mGiruda && card1.number < card2.number) return false;

            // 선무늬가 승리
            if (card1.shape == firstShape && card2.shape != firstShape) return true;
            if (card1.shape != firstShape && card2.shape == firstShape) return false;

            // 둘 다 선무늬고 숫자가 높으면 승리
            if (card1.shape == firstShape && card2.shape == firstShape && card1.number > card2.number) return true;
            if (card1.shape == firstShape && card2.shape == firstShape && card1.number < card2.number) return false;

            // 그외는 그놈이 그놈
            return true;
        }

        /// <summary>
        /// 현재까지 나온 그림카드 수를 센다.
        /// </summary>
        private int CountGrim()
        {
            int numGrim = 0;

            for (int i = 0; i < gameInfo.roundCardIdx; i++)
            {
                if (gameInfo.round.cardStack[i].number >= 10)
                    numGrim++;
            }

            return numGrim;
        }

        /// <summary>
        /// 내가 마지막 차례일 때 낼 카드를 고른다.
        /// </summary>
        private HandedCard LastPlayer(HandedCard nextCard, ArrayList posCards)
        {
            bool firstShapeExist = false;
            bool win = true;

            // 선무늬가 있는지 확인
            foreach (HandedCard card in posCards)
            {
                if (card.shape == gameInfo.round.cardStack[0].shape)
                {
                    firstShapeExist = true;
                }
            }

            // 선무늬가 있다.
            if (firstShapeExist)
            {
                foreach (HandedCard card in posCards)
                {
                    nextCard = card;

                    for (int i = 0; i < 4; i++)
                    {
                        if (CompareCards(nextCard, gameInfo.round.cardStack[i]) == false)
                        {
                            win = false;
                            break;
                        }
                    }

                    // 이길 수 있다.
                    if (win)
                        return nextCard;
                }
                // 이길 수 없다.
                foreach (HandedCard card in posCards)
                {
                    if (nextCard.number > card.number)
                        nextCard = card;
                }
                return nextCard;
            }
            // 선무늬가 없다.
            else
            {
                if (CountGrim() >= 2)
                {
                    // 기루다나 조커가 있고 이길 수 있으면 그 카드를 낸다.
                    foreach (HandedCard card in posCards)
                    {
                        if (card.shape == mGiruda)
                        {
                            for (int i = 0; i < gameInfo.roundCardIdx; i++)
                            {
                                if (CompareCards(card, gameInfo.round.cardStack[i]) == false)
                                    win = false;
                            }

                            if (win)
                                nextCard = card;
                        }
                    }
                    if (win)
                        return nextCard;
                }
                // 그림카드가 2장 미만이거나 이길 수 없거나 기루다가 없으면 조커가 아닌 것 중에 숫자가 가장 작은 카드를 낸다. 없으면 기루다 내고.
                foreach (HandedCard card in posCards)
                {
                    if (card.shape != mGiruda)
                        if (nextCard.number > card.number && card.shape != Shape.Joker)
                            nextCard = card;
                }
                return nextCard;
            }
        }

        /// <summary>
        /// 현재 라운드에서 지금까지의 승자의 편이 누구인지 확인한다.
        /// return 값은 지금까지의 승자의 position
        /// </summary>
        private int FindWinner()
        {
            if (gameInfo.roundIdx > 0)
            {
                int winner = gameInfo.roundHistory[gameInfo.roundIdx - 1].winner;
                int idx = winner;

                for (int i = 0; i < gameInfo.roundCardIdx; i++)
                {
                    if (CompareCards(gameInfo.round.cardStack[idx], gameInfo.round.cardStack[winner]))
                    {
                        winner = idx;
                    }
                    idx = (idx + 1) % 5;
                }
                return winner;
            }
            else
                return gameInfo.attackPlayer.position;
        }

        /// <summary>
        /// 다른사람의 카드 정보
        /// </summary>
        public override void RoundCardThrowEvent()
        {
        }

        /// <summary>
        /// 각라운드의종료, 각 라운드가 종료되면 호출된다. 각 라운드의 승자는 gameinfo의 winner를 참조한다
        /// </summary>
        public override void RoundCompleteEvent()
        {
        }

        /// <summary>
        /// 게임의 종료, 게임이 종료되었을때 실행될 코드는 여기에 추가한다.
        /// </summary>
        public override void GameEndEvent()
        {

        }
    }
}